﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
#if NETFX_CORE
using System.Composition;
#else
using System.ComponentModel.Composition;
#endif
using System.Diagnostics.Contracts;
using System.Linq;
using System.Threading;
using DaveSexton.Labs.Properties;

namespace DaveSexton.Labs
{
	/// <summary>
	/// Dynamic <see cref="Lab"/> that accepts an <see cref="Action"/> delegate for the 
	/// <see cref="Lab.Main"/> method, without requiring an explicit class to be defined.
	/// </summary>
	[Category("Anonymous")]
	[Description("")]
	[PartNotDiscoverable]
	public sealed class AnonymousLab : Lab
	{
		#region Public Properties
		#endregion

		#region Private / Protected
		private static int count;

		protected internal override IEnumerable<IExperiment> Experiments
		{
			get
			{
				return experiments == null ? base.Experiments : experiments;
			}
		}

		private readonly Action main;
		private readonly Func<bool> canExecute;
		private readonly IList<IExperiment> experiments;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="AnonymousLab" /> class.
		/// </summary>
		public AnonymousLab(bool enabled, Action main)
			: base(CreateName(), enabled)
		{
			Contract.Requires(main != null);

			this.main = main;
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="AnonymousLab" /> class.
		/// </summary>
		public AnonymousLab(bool enabled, params IExperiment[] experiments)
			: base(CreateName(), enabled)
		{
			Contract.Requires(experiments != null);

			this.experiments = experiments.ToList().AsReadOnly();
			this.main = RunExperiments;

			Contract.Assume(this.experiments.IsReadOnly);
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="AnonymousLab" /> class.
		/// </summary>
		public AnonymousLab(bool enabled, Func<bool> canExecute, Action main)
			: base(CreateName(), enabled)
		{
			Contract.Requires(canExecute != null);
			Contract.Requires(main != null);

			this.canExecute = canExecute;
			this.main = main;
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="AnonymousLab" /> class.
		/// </summary>
		public AnonymousLab(bool enabled, Func<bool> canExecute, params IExperiment[] experiments)
			: base(CreateName(), enabled)
		{
			Contract.Requires(canExecute != null);
			Contract.Requires(experiments != null);

			this.canExecute = canExecute;
			this.experiments = experiments.ToList().AsReadOnly();
			this.main = RunExperiments;

			Contract.Assume(this.experiments.IsReadOnly);
		}
		#endregion

		#region Methods
		[ContractInvariantMethod]
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Required for code contracts.")]
		private void ObjectInvariant()
		{
			Contract.Invariant(!IsProxy);
			Contract.Invariant(main != null);
			Contract.Invariant(experiments == null || experiments.IsReadOnly);
		}

		private static string CreateName()
		{
#if !WINDOWS_PHONE_70
			Contract.Ensures(!string.IsNullOrWhiteSpace(Contract.Result<string>()));
#else
			Contract.Ensures(!string.IsNullOrEmpty(Contract.Result<string>()));
#endif

			int number = Interlocked.Increment(ref count);

			string name = number == 1
				? Resources.Anonymous
				: Resources.Anonymous + "-" + number;

#if !WINDOWS_PHONE_70
			Contract.Assume(!string.IsNullOrWhiteSpace(name));
#else
			Contract.Assume(!string.IsNullOrEmpty(name));
#endif

			return name;
		}

		public override bool CanExecute()
		{
			return (canExecute == null)
				? base.CanExecute()
				: canExecute();
		}

		protected override void Main()
		{
			main();
		}
		#endregion
	}
}